import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  ViewContainerRef,
  ElementRef,
  ChangeDetectorRef,
  OnDestroy,
  ViewChild,
  TemplateRef,
  Output,
  EventEmitter,
  forwardRef,
  SimpleChanges,
  OnChanges,
  Renderer2,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { Subject } from 'rxjs';
import { takeUntil, tap, filter } from 'rxjs/operators';
import { cloneDeep as _cloneDeep } from 'lodash';
import { isBefore } from 'date-fns';
import {
  InputBoolean,
  OverlayService,
  I18NService,
  DateService,
  StandardDate,
  LvDateRangeTag,
  LvConfig,
  isEmpty,
  LvSizeLD,
  showItem,
  TypeUtils,
  ExtendOverlayConfig,
} from '../core/api';
import { ADJUST_POSITION_MAP, getPosition } from './date-picker-position';
import { LvPresetRanges, LvDateTimeOption } from './interface';

@Component({
  selector: 'lv-date-range-picker',
  exportAs: 'lvDateRangePicker',
  templateUrl: './date-range-picker.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showItem],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof DateRangePickerComponent => DateRangePickerComponent),
      multi: true,
    },
  ],
  host: {
    '[class.lv-date-range-picker]': 'true',
    '[class.lv-date-range-picker-inline]': 'lvInline',
    '[style.minWidth]': 'lvInline ? getPanelWidth +"px" : null',
    '[class.lv-date-range-picker-size-large]': 'lvSize === "large"',
  },
})
export class DateRangePickerComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
  @Input() lvPanelClass: string = '';

  @Input() lvSize: LvSizeLD = 'default';

  @Input() @InputBoolean() lvInline = false; // 内联展示模式

  @Input() lvPresetRanges: LvPresetRanges[];

  @Input() @InputBoolean() lvDisabled = false; // 是否禁用

  @Input() lvCurrentInput: LvDateRangeTag = 'start'; // 用于内联模式选中start和end

  @Output() lvOpenChange = new EventEmitter<boolean>(); // 切换面板回调函数

  @Output() lvRangeTagChange = new EventEmitter<any>(); // 当前输入框变化时的回调函数

  @Input() @InputBoolean() lvOnlyShowActiveCell = false; // 是否只显示当前状态的单元格

  @Input() @InputBoolean() lvPanelTitleReadonly = false; // 面板头部标题是否可点击

  @Input() lvFooterExtra: TemplateRef<object> | null = null;

  @Output() lvActiveDateChange = new EventEmitter<any>();

  @Input() lvShowTime: boolean | LvDateTimeOption = false;

  @Input() lvCalendarCell: TemplateRef<Date> | null = null; // 自定义日期单元格

  @Input() lvCalendarYearCell: TemplateRef<Date> | null = null; // 自定义年份单元格

  @Input() lvCalendarMonthCell: TemplateRef<Date> | null = null; // 自定义月份单元格

  @Input() @InputBoolean() lvReadonly = LvConfig.datePickerOptions.lvReadonly; // NEED_DO支持输入

  @ViewChild('datePanelTpl', { static: true }) _datePanelTpl: TemplateRef<any>;

  @Input() @InputBoolean() lvShowClear = LvConfig.datePickerOptions.lvShowClear; // 是否支持清除

  @Input() lvTimezoneOffset: number = LvConfig.datePickerOptions.lvTimezoneOffset; // 本地时间与格林威治标准时间 (GMT) 的分钟差

  @Input() lvPlaceholder = [this._i18n.get('startDate'), this._i18n.get('endDate')];

  @Input() lvFormat: string | ((date: Date) => string) = LvConfig.datePickerOptions.lvFormat; // 日期格式

  @Input() lvDisabledDate: (date: Date, selectedDate: Date[], tag: LvDateRangeTag) => boolean; // 禁用日期

  @Input() @InputBoolean() lvBackfillDisabledDate = LvConfig.datePickerOptions.lvBackfillDisabledDate; // 回填已禁用的时间点

  @Input() lvCalendarYearOperate: { show?: boolean; disableChange?: boolean } = { show: true, disableChange: false }; // 面板头部年份操作

  @Input() lvCalendarMonthOperate: { show?: boolean; disableChange?: boolean } = { show: true, disableChange: false }; // 面板头部月份操作

  @Input() @InputBoolean() lvNowAsBaseTime =
    LvConfig.datePickerOptions.lvNowAsBaseTime === undefined ? true : LvConfig.datePickerOptions.lvNowAsBaseTime; // 是否以当前时间作为基准时间，否则以零点作为基准时间

  public overlayRef;
  public showTimeOriginValue; // 配置showTime时，保存打开面板后的初始值，用于外部点击关闭时恢复初始值
  public displayEndValue = '';
  public open: boolean = false;
  public displayStartValue = '';
  public animationState = 'void';
  public originValue = [null, null];
  public _open$ = new Subject<boolean>();
  public _value: [Date, Date] = [null, null];

  public currentInput: LvDateRangeTag = 'start';
  private _direction;

  private _isClear = false; // 带时间选择时，用于记录在下拉面板打开过程中是否点击删除
  private typeUtils = TypeUtils;
  private _initValue = [null, null];
  private _destroy$ = new Subject<void>();
  private _refresh$ = new Subject<void>();

  private _openState$ = new Subject<boolean>();
  constructor(
    private _viewContainerRef: ViewContainerRef,
    private _overlayService: OverlayService,
    private _overlay: Overlay,
    private _elementRef: ElementRef,
    private _renderer: Renderer2,
    private _cdr: ChangeDetectorRef,
    private _i18n: I18NService,
    public dateService: DateService,
  ) {}

  get empty(): boolean {
    return isEmpty(this.displayStartValue) && isEmpty(this.displayEndValue);
  }

  get getSelectedClass(): string {
    if (this.open || this.lvInline) {
      if (this.currentInput === 'start') {
        return 'lv-date-picker-selected-start';
      } else if (this.currentInput === 'end') {
        return 'lv-date-picker-selected-end';
      }
    }
    return undefined;
  }

  getDateDisableState = (date, tag, isPreset): boolean => {
    const _tag = tag ? tag : this.currentInput;
    const _date = new StandardDate(date);

    // 预设范围不需要禁用开始时间前和结束时间后的时间
    let res1;
    let res2;
    res1 = !isPreset && this.isRangeDisableDate(_date, _tag);
    if (this.lvDisabledDate) {
      res2 = this.lvDisabledDate(date, _cloneDeep(this._value), _tag);
    } else {
      res2 = false;
    }
    return res1 || res2;
  };

  setRefresh(): void {
    this._refresh$.next();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  activeDateChange(e): void {
    this.lvActiveDateChange.emit(e);
  }

  // 关闭下拉
  close(): void {
    if (this._existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  _existOverlayRef(): any {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  // 订阅更新视图
  _subRefresh(): void {
    this._refresh$.pipe(takeUntil(this._destroy$)).subscribe((): void => this._cdr.markForCheck());
  }

  currentInputChange(e): void {
    this.currentInput = e;
    this.lvRangeTagChange.emit(this.currentInput);
    this._cdr.detectChanges();
  }

  initFormat(): void {
    if (this.lvShowTime) {
      this.lvFormat = 'yyyy-MM-dd HH:mm:ss';
    } else {
      this.lvFormat = 'yyyy-MM-dd';
    }
  }

  beforeClose(): void {
    if (this._direction === 'up') {
      this.animationState = 'hideUp';
    } else {
      this.animationState = 'hideDown';
    }
    this._cdr.markForCheck();
  }

  ngOnDestroy(): void {
    if (this.overlayRef?.overlayRef.hasAttached()) {
      this.overlayRef.overlayRef.dispose();
    }

    this._destroy$.next();
    this._destroy$.complete();
  }

  onBlur(): void {
    if (!this.lvReadonly) {
      if (!this.open) {
        this.displayStartValue = '';
        this.displayEndValue = '';
      }

      this.dateChange(this._value);
    }
  }

  toggleOpen(): void {
    if (!this.lvDisabled) {
      this._open$.next((this.open = !this.open));
      this.currentInput = this.open ? 'start' : null;
      this.lvRangeTagChange.emit(this.currentInput);
    }
  }

  clear(): void {
    this._isClear = true;
    this.displayStartValue = '';
    this.displayEndValue = '';
    this.originValue = [null, null];
    this._value = [null, null];
    this.emitRangeDate(this._value);
  }

  ngOnInit(): void {
    // 默认格式化处理
    if (!this.lvFormat) {
      this.initFormat();
    } else if (typeof this.lvFormat === 'function') {
      this.lvReadonly = true;
    }
    this._subOpen();
    this._subRefresh();
  }

  onAnimationDone(event): void {
    const isUp = event.fromState === 'showUp' && event.toState === 'hideUp';
    const isDown = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (isUp || isDown) {
      this._openState$.next(false);
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvTimezoneOffset && !changes.lvTimezoneOffset.firstChange) {
      this.initValue(this._initValue);
    }
    if (changes.lvCurrentInput?.currentValue !== changes.lvCurrentInput?.previousValue) {
      this.onFocus(null, this.lvCurrentInput);
    }
  }

  isRangeDisableDate(date: StandardDate, tag: string): boolean | void {
    if (tag === 'start' && this._value[1]) {
      return date.isCompareDay(new StandardDate(this._value[1]), 'after');
    }
    if (tag === 'end' && this._value[0]) {
      return date.isCompareDay(new StandardDate(this._value[0]), 'before');
    }
    return null;
  }

  emitRangeDate(value): void {
    let offsetDate = value;
    if (this.lvTimezoneOffset || this.lvTimezoneOffset === 0) {
      offsetDate = [
        this.dateService.getTimezoneOffsetDate(value[0], this.lvTimezoneOffset),
        this.dateService.getTimezoneOffsetDate(value[1], this.lvTimezoneOffset),
      ];
    }
    this._onChange(offsetDate);
  }

  // 订阅切换面板的流
  _subOpen(): void {
    this._open$
      .pipe(
        tap((res: boolean): void => {
          this.open = res;
          if (!res) {
            this.close();
            this.setRefresh();
          }
        }), // 关闭
        filter((res): any => res), // 打开
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this._openPanelOverlay();
      });
  }

  dateChange(dateRange): void {
    if (!dateRange) {
      return;
    }

    if (this.typeUtils.isNotNil(dateRange[0]) && this.typeUtils.isNil(dateRange[1]) && !this.lvShowTime) {
      this.currentInput = 'end';
    }
    if (this.typeUtils.isNotNil(dateRange[1]) && this.typeUtils.isNil(dateRange[0]) && !this.lvShowTime) {
      this.currentInput = 'start';
    }

    this.handleDateChange(dateRange);

    this.lvRangeTagChange.emit(this.currentInput);
    this._cdr.markForCheck();
  }

  afterOpen(overlayInstance): void {
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this._cdr.markForCheck();
    });
  }

  // 打开下拉panel overlay
  _openPanelOverlay(): void {
    if (!this._existOverlayRef()) {
      const config = new OverlayConfig({
        hasBackdrop: false,
        scrollStrategy: this._overlay.scrollStrategies.block(),
        positionStrategy: this._overlay
          .position()
          .flexibleConnectedTo(this._elementRef)
          .withPositions(ADJUST_POSITION_MAP),
      });

      const extendConfig = this.getExtendConfig();

      const portal = new TemplatePortal(this._datePanelTpl, this._viewContainerRef);
      this.overlayRef = this._overlayService.open(portal, config, extendConfig);
      this.setRefresh();
    }
  }

  checkDate(): void {
    const startDate = this._value[0];
    const endDate = this._value[1];
    if (startDate || endDate) {
      const startInvalid = !this._value[0] || this.checkDateDisableStatus(this._value[0], 'start');
      const endInvalid = !this._value[1] || this.checkDateDisableStatus(this._value[1], 'end');

      if ((startInvalid || endInvalid) && !this.lvBackfillDisabledDate) {
        this._value = [null, null];
        this.displayStartValue = '';
        this.displayEndValue = '';

        if (this.originValue[0] || this.originValue[1]) {
          this.originValue = [null, null];
          this.emitRangeDate(this._value);
        }
      }
    }
  }

  initValue(value): void {
    this._initValue = value;
    this.displayStartValue = this.dateService.format(value[0], this.lvFormat, this.lvTimezoneOffset);
    this.displayEndValue = this.dateService.format(value[1], this.lvFormat, this.lvTimezoneOffset);
    let offsetDate = value;
    if (this.lvTimezoneOffset || this.lvTimezoneOffset === 0) {
      offsetDate = [
        this.dateService.getTimezoneOffsetDate(value[0], this.lvTimezoneOffset, true),
        this.dateService.getTimezoneOffsetDate(value[1], this.lvTimezoneOffset, true),
      ];
    }
    this._value = offsetDate as [Date, Date];
    this.originValue = _cloneDeep(offsetDate);
    this._cdr.detectChanges();
  }

  onInputChange(value: string, tag: LvDateRangeTag): void {
    // lvFormat为function时不做输入的校验，故lvFormat断言为string
    const date = this.dateService.parseDate(value, this.lvFormat as string);
    if (
      !this.dateService.isValidDate(date) ||
      value !== this.dateService.format(date, this.lvFormat) ||
      this.checkDateDisableStatus(date, tag)
    ) {
      return;
    }
    let [startDate, endDate] = this._value;

    if (tag === 'start') {
      startDate = date;
    } else if (tag === 'end') {
      endDate = date;
    }

    if (startDate === null || endDate === null || isBefore(startDate, endDate)) {
      this._value = [startDate, endDate];
    } else {
      this._value = [endDate, startDate];
    }
    this._initValue = [...this._value];
  }

  checkDateDisableStatus(v, tag: LvDateRangeTag): boolean {
    if (!v) {
      return false;
    }
    let isDateDisabled = false;
    let isTimeDisabled = false;
    const date = new StandardDate(v);

    if (this.lvDisabledDate) {
      isDateDisabled = this.lvDisabledDate(date.localDate, _cloneDeep(this._value), tag);
    }
    if (date && typeof this.lvShowTime !== 'boolean') {
      const hours = date.getHours();
      const minutes = date.getMinutes();
      const seconds = date.getSeconds();
      if (this.lvShowTime.disabledHour) {
        isTimeDisabled = this.lvShowTime.disabledHour(hours, tag);
      }
      if (!isTimeDisabled && this.lvShowTime.disabledMinute) {
        isTimeDisabled = this.lvShowTime.disabledMinute(hours, minutes, tag);
      }
      if (!isTimeDisabled && this.lvShowTime.disabledSecond) {
        isTimeDisabled = this.lvShowTime.disabledSecond(hours, minutes, seconds, tag);
      }
    }
    return isDateDisabled || isTimeDisabled;
  }

  getExtendConfig(): ExtendOverlayConfig {
    return {
      internalTriggerClose: this._internalTriggerClose,
      beforeClose: this._beforeClose,
      afterClose: this._afterClose,
      afterOpen: this._afterOpen,
    };
  }

  _internalTriggerClose = (): void => {
    this.internalTriggerClose();
  };

  _beforeClose = (): Promise<void> => {
    this.checkDate();
    return new Promise<void>((resolve): void => {
      this.beforeClose();
      const closesubscribe = this._openState$.subscribe((open): void => {
        if (!open) {
          closesubscribe.unsubscribe();
          resolve();
        }
      });
    });
  };

  _afterClose = (): void => {
    this.lvOpenChange.emit(this.open);
  };

  _afterOpen = (overlayInstance): void => {
    if (this.lvShowTime) {
      this._isClear = false;
      this.showTimeOriginValue = _cloneDeep(this._value);
    }
    // 设置z-index
    this._renderer.addClass(overlayInstance.overlayRef.hostElement, 'lv-date-z-index');
    this.afterOpen(overlayInstance);
    this.lvOpenChange.emit(this.open);
  };

  onFocus(e, tag): void {
    const flag = tag !== this.currentInput && this._value[0] && this._value[1] && this._value[0] > this._value[1];
    if (flag) {
      [this._value[0], this._value[1]] = [this._value[1], this._value[0]];
      this._value = [...this._value];
      this.dateChange(this._value);
    }
    this.currentInput = tag;
    if (!this.lvInline) {
      this._open$.next(true);
    }
    this.lvRangeTagChange.emit(this.currentInput);
    if (this.lvBackfillDisabledDate) {
      return;
    }
    if (tag === 'start') {
      if (this._value[1] && this.checkDateDisableStatus(this._value[1], 'end')) {
        const startDate = this._value[0];
        this._value = [startDate, null];
        if (this.displayEndValue !== '') {
          this.displayEndValue = '';
        }
      }
    } else if (tag === 'end') {
      if (this._value[0] && this.checkDateDisableStatus(this._value[0], 'start')) {
        const endDate = this._value[1];
        this._value = [null, endDate];
        if (this.displayStartValue !== '') {
          this.displayStartValue = '';
        }
      }
    }
  }

  writeValue(value: any): void {
    if (value instanceof Array) {
      if (value[0] && value[1]) {
        if (value[0].getTime() > value[1].getTime() && !this.lvInline) {
          // eslint-disable-next-line no-console
          console.error('Start date can not be greater than end date.');
          return;
        }
        if (
          (this.checkDateDisableStatus(value[0], 'start') || this.checkDateDisableStatus(value[1], 'end')) &&
          !this.lvBackfillDisabledDate
        ) {
          // eslint-disable-next-line no-console
          console.error('DatePicker(range) component initialization value is disabled.');
          return;
        }
        this.initValue(value);
      } else {
        if (value[0] && !value[1]) {
          // eslint-disable-next-line no-console
          console.error('The end date cannot be empty.');
          return;
        }
        if (!value[0] && value[1]) {
          // eslint-disable-next-line no-console
          console.error('The start date cannot be empty.');
          return;
        }
        this.initValue([null, null]);
      }
    } else if (this.typeUtils.isNull(value)) {
      this.initValue([null, null]);
    } else {
      if (value) {
        // eslint-disable-next-line no-console
        console.error('The format of date range is incorrect.');
      }
    }
  }

  internalTriggerClose(): void {
    if (this.lvShowTime && !this._isClear) {
      this._value = _cloneDeep(this.showTimeOriginValue);
      this._initValue = _cloneDeep(this.showTimeOriginValue);
      this.originValue = _cloneDeep(this.showTimeOriginValue);

      const offsetStartDate =
        this.lvTimezoneOffset || this.lvTimezoneOffset === 0
          ? this.dateService.getTimezoneOffsetDate(_cloneDeep(this.showTimeOriginValue[0]), this.lvTimezoneOffset)
          : _cloneDeep(this.showTimeOriginValue[0]);
      this.displayStartValue = this.dateService.format(offsetStartDate, this.lvFormat, this.lvTimezoneOffset);
      const offsetEndDate =
        this.lvTimezoneOffset || this.lvTimezoneOffset === 0
          ? this.dateService.getTimezoneOffsetDate(_cloneDeep(this.showTimeOriginValue[1]), this.lvTimezoneOffset)
          : _cloneDeep(this.showTimeOriginValue[1]);
      this.displayEndValue = this.dateService.format(offsetEndDate, this.lvFormat, this.lvTimezoneOffset);

      this.emitRangeDate(this._value);
      this._cdr.markForCheck();
    }
    if (this.lvShowTime && this._isClear) {
      this.clear();
    }
    this._open$.next(false);
  }

  getShowTimeState(): boolean | LvDateTimeOption {
    if (this.lvShowTime instanceof Object) {
      const showTimeConfig: LvDateTimeOption = {};
      if (this.lvShowTime.format) {
        showTimeConfig.format = this.lvShowTime.format;
      }
      if (this.lvShowTime.disabledHour) {
        showTimeConfig.disabledHour = (hour, tag): any => {
          const _tag = tag ? tag : this.currentInput;
          return this.lvShowTime instanceof Object && this.lvShowTime.disabledHour(hour, _tag);
        };
      }
      if (this.lvShowTime.disabledMinute) {
        showTimeConfig.disabledMinute = (hour, minute, tag): any => {
          const _tag = tag ? tag : this.currentInput;
          return this.lvShowTime instanceof Object && this.lvShowTime.disabledMinute(hour, minute, _tag);
        };
      }
      if (this.lvShowTime.disabledSecond) {
        showTimeConfig.disabledSecond = (hour, minute, second, tag): any => {
          const _tag = tag ? tag : this.currentInput;
          return this.lvShowTime instanceof Object && this.lvShowTime.disabledSecond(hour, minute, second, _tag);
        };
      }
      return showTimeConfig;
    } else {
      return this.lvShowTime;
    }
  }

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};

  private handleDateChange(dateRange): void {
    let isChange = false;
    if (dateRange[0] !== this.originValue[0] && !this.checkDateDisableStatus(dateRange[0], 'start')) {
      this.displayStartValue = this.dateService.format(dateRange[0], this.lvFormat);
      if (this.lvTimezoneOffset || this.lvTimezoneOffset === 0) {
        const offsetStartDate = this.dateService.getTimezoneOffsetDate(dateRange[0], this.lvTimezoneOffset);
        this.displayStartValue = this.dateService.format(offsetStartDate, this.lvFormat, this.lvTimezoneOffset);
        this._initValue[0] = offsetStartDate;
      }
      isChange = true;
    }
    if (dateRange[1] !== this.originValue[1] && !this.checkDateDisableStatus(dateRange[1], 'end')) {
      this.displayEndValue = this.dateService.format(dateRange[1], this.lvFormat);
      if (this.lvTimezoneOffset || this.lvTimezoneOffset === 0) {
        const offsetEndDate = this.dateService.getTimezoneOffsetDate(dateRange[1], this.lvTimezoneOffset);
        this.displayEndValue = this.dateService.format(offsetEndDate, this.lvFormat, this.lvTimezoneOffset);
        this._initValue[1] = offsetEndDate;
      }
      isChange = true;
    }
    const flag = isChange && ((this._value[0] && this._value[1]) || this.lvInline);
    if (flag) {
      this.originValue[0] = _cloneDeep(dateRange[0]);
      this.originValue[1] = _cloneDeep(dateRange[1]);
      this.emitRangeDate(this._value);
    }
  }
}
